﻿/*
 * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 22/06/2012
 * Time: 05:43
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace CSharpExt.Net.Sockets
{
    public class Accepted
    {
        #region Private Data
        
        private System.Net.Sockets.TcpListener _listener;
        
        private System.Net.Sockets.Socket _acceptedSocket;
        
        private System.Net.Sockets.TcpClient _acceptedClient;
        
        private IAsyncResult _result;
        
        #endregion
        
        #region Constructor
        
        internal Accepted(TcpListener l)
        {
            _listener = l;
        }
        
        #endregion
        
        #region Properties
        
        public Socket SocketAccepted
        {
            get 
            {
                if (_acceptedSocket != null)
                    return _acceptedSocket; 
                if (_acceptedClient != null)
                    return _acceptedClient.Client;
                else
                    return null;
            }
        }
        
        public System.Net.Sockets.TcpClient ClientAccepted
        {
            get 
            {
                if (_acceptedClient != null)
                    return _acceptedClient;
                if (_acceptedSocket != null)
                    return new TcpConnect(){ Client = _acceptedSocket };
                else
                    return null;
            }
        }
        
        // ------------------------------------------------------------------
        
        internal System.Net.Sockets.TcpListener Listener
        {
            get { return _listener; }
        }
        
        internal IAsyncResult Result
        {
            set { _result = value; }
        }
        
        #endregion
        
        #region Operations

        public bool WaitAccept(int timeout)
        {
            lock (this)
            {
                if (_acceptedClient == null && _acceptedSocket == null)
                    Monitor.Wait(this, timeout);
                
                return _acceptedSocket != null || _acceptedSocket != null;
            }
        }
        
        #endregion
        
        #region Implementation
        
        internal void Accept(System.Net.Sockets.Socket socket)
        {
            lock (this)
            {
                _acceptedSocket = socket;
                
                Monitor.Pulse(this);
            }
        }
        
        internal void Accept(System.Net.Sockets.TcpClient client)
        {
            lock (this)
            {
                _acceptedClient = client;
                
                Monitor.Pulse(this);
            }
        }
        
        #endregion
    }
    
    public class Connected
    {
        #region Private Data
        
        private TcpConnect _client;
        
        private bool _connected;
        
        #endregion
        
        #region Constructor
        
        internal Connected(TcpConnect c)
        {
            _client = c;
        }
        
        #endregion
        
        #region Properties
        
        internal TcpConnect Client
        {
            get { return _client; }
        }
        
        #endregion
        
        #region Operations

        public bool WaitConnect(int timeout)
        {
            lock (this)
            {
                if (!_connected)
                    Monitor.Wait(this, timeout);
                
                return _connected;
            }
        }
        
        #endregion
        
        #region Implementation
        
        internal void IsConnected()
        {
            lock (this)
            {
                _connected = true;
                
                Monitor.Pulse(this);
            }
        }
        
        #endregion
    }

    public static class TcpUtils
    {
        #region AcceptSocketAsync
  
        /// <summary>
        /// Permette di iniziare una Accept su una socket, ma comunque di continuare se l'accept 
        /// non viene fatta entro un termine massimo di tempo.
        /// 
        /// Il loop deve essere fatto su Accepted:
        /// 
        ///         Accepted as = listener.AcceptSocketAsync();
        /// 
        ///         while(!as.WaitAccept(1000))
        ///         {
        ///             // do something
        ///         }
        /// 
        ///         Socket s = as.SocketAccepted; 
        /// 
        /// Accepted puo' essere usato UNA sola volta.
        /// 
        /// </summary>
        public static Accepted AcceptSocketAsync(this TcpListener l)
        {
            Accepted accepted = new Accepted(l);
            
            accepted.Result = l.BeginAcceptSocket(onAcceptedSocket, accepted);
            
            return accepted;
        }
        
        public static Accepted AccepClientAsync(this TcpListener l)
        {
            Accepted accepted = new Accepted(l);
            
            accepted.Result = l.BeginAcceptTcpClient(onAcceptedClient, accepted);
            
            return accepted;
        }

        // ------------------------------------------------------------------
        
        private static void onAcceptedSocket(IAsyncResult ar)
        {
            Accepted accepted = ar.AsyncState as Accepted;
            
            accepted.Accept(accepted.Listener.EndAcceptSocket(ar));
        }
        
        private static void onAcceptedClient(IAsyncResult ar)
        {
            Accepted accepted = ar.AsyncState as Accepted;
            
            accepted.Accept(accepted.Listener.EndAcceptTcpClient(ar));
        }
        
        #endregion
        
        #region ConnectAsync
        
        /// <summary>
        /// Permette di iniziare una Connect, ma comunque di continuare se la Connect
        /// non viene fatta entro un termine massimo di tempo.
        /// 
        /// Il loop deve essere fatto su Connected:
        /// 
        /// 
        ///         Connected c = client.ConnectAsync();
        /// 
        ///         while(!c.WaitConnect(1000))
        ///         {
        ///             // do something
        ///         }
        /// 
        ///         Socket s = as.SocketAccepted; 
        /// 
        /// Accepted puo' essere usato UNA sola volta.
        /// </summary>
        public static Connected ConnectAsync(this TcpConnect c, String host, int port)
        {
            Connected connected = new Connected(c);
            
            c.BeginConnect(host, port, onConnectedAsync, connected);
            
            return connected;
        }
        
        public static Connected ConnectAsync(this TcpConnect c, IPAddress address, int port)
        {
            Connected connected = new Connected(c);
            
            c.BeginConnect(address, port, onConnectedAsync, connected);
            
            return connected;
        }
        
        public static Connected ConnectAsync(this TcpConnect c, EndPoint ep)
        {
            if (ep as DnsEndPoint != null)
            {
                DnsEndPoint dnsep = ep as DnsEndPoint;
                return c.ConnectAsync(dnsep.Host, dnsep.Port);
            }
            else
            {
                IPEndPoint ipep = ep as IPEndPoint;
                return c.ConnectAsync(ipep.Address, ipep.Port);
            }
        }
        
        // ------------------------------------------------------------------
        
        private static void onConnectedAsync(IAsyncResult ar)
        {
            Connected connected = ar.AsyncState as Connected;
            
            connected.Client.EndConnect(ar);
            
            connected.IsConnected();
        }
        
        #endregion
    }
    
}
